# coding: utf8

import hashlib
import random
import datetime

from django.db import models

from protocol import BaiduProtocol
from umeng_tool.analyse.data_clear import access_manage, province_manage, channel_data
from umeng_tool.analyse import consts
from umeng_tool.analyse.utils import DatetimeUtils
import logging


run_log = logging.getLogger('run')
err_log = logging.getLogger('err')


class UmengSDK(models.Model):
	"""
	友盟信息
	"""
	sdk_type = models.CharField(verbose_name='友盟SDK类型', default='Android', max_length=50)
	sdk_version = models.CharField(verbose_name='友盟SDK版本号', max_length=50)

	def __str__(self):
		return '%s %s' % (self.sdk_type, self.sdk_version)


class AppInfo(models.Model):
	"""
	目标App信息
	"""
	APPKEY_TEST = '556d262a67e58eaf050034d6'
	APPKEY_CATEGORY = {
		APPKEY_TEST: '测试用APP',
	}

	appkey = models.CharField(verbose_name='友盟AppKey', max_length=255, choices=APPKEY_CATEGORY.items())
	package_name = models.CharField(verbose_name='应用包名', max_length=255)
	app_version = models.CharField(verbose_name='应用版本名称', max_length=255)
	channel = models.CharField(verbose_name='应用渠道', max_length=255)
	version_code = models.CharField(verbose_name='应用版本号', max_length=255)
	_umeng_sdk = models.ForeignKey(UmengSDK, verbose_name='友盟SDK', null=True)

	def __str__(self):
		return '%s %s-%s' % (self.package_name, self.app_version, self.version_code)


class Device(models.Model):
	"""
	设备种类
	"""
	device_model = models.CharField(verbose_name='设备型号', max_length=255)
	height = models.IntegerField(verbose_name='屏幕高')
	width = models.IntegerField(verbose_name='屏幕宽')
	device_manufacturer = models.CharField(verbose_name='设备生产商', max_length=255)
	cpu = models.CharField(verbose_name='设备CPU', max_length=255)
	device_brand = models.CharField(verbose_name='设备品牌', max_length=255)
	os = models.CharField(verbose_name='系统', max_length=30, default='Android')
	os_version = models.CharField(verbose_name='系统版本号', max_length=100)
	device_manuid = models.CharField(verbose_name='系统固件版本号', max_length=255)
	device_weight = models.IntegerField(verbose_name='设备市场占有权重')

	WEIGHT_RATIO_TOTAL = consts.WEIGHT_RATIO_TOTAL

	@property
	def resolution(self):
		return '%s * %s' % (self.height, self.width)

	@classmethod
	def make_device_probability_ratio_area(cls):
		"""
		生成设备概率因子区间
		:return: {device: (ratio_min, ratio_max)}
		"""
		wa = {}
		all_device = cls.objects.all()
		total_device_weight = sum([d.device_weight for d in all_device])
		index = 1
		for d in all_device:
			wa[d] = (
				int(index * cls.WEIGHT_RATIO_TOTAL / total_device_weight) if index > 1 else 1,
				int((index + d.device_weight) * cls.WEIGHT_RATIO_TOTAL / total_device_weight) if index + d.device_weight < total_device_weight else int(cls.WEIGHT_RATIO_TOTAL)
			)
			index += d.device_weight
		return wa

	@classmethod
	def get_device_random(cls):
		"""
		随机获取一种设备
		:return: 设备对象
		:rtype: Device
		"""
		probability_ratio = random.randint(1, cls.WEIGHT_RATIO_TOTAL + 1)
		for device, area in cls.make_device_probability_ratio_area().items():
			if area[0] <= probability_ratio <= area[1]:
				return device


class PhoneDevice(models.Model):
	"""
	模拟设备
	"""
	CARRIER_YIDONG = 0
	CARRIER_LIANTONG = 1
	CARRIER_DIANXIN = 2
	CARRIER_TYPE = {
		CARRIER_YIDONG: '中国移动',
		CARRIER_LIANTONG: '中国联通',
		CARRIER_DIANXIN: '中国电信',
	}

	ACCESS_WIFI = 0
	ACCESS_3G = 1
	ACCESS_2G = 2
	ACCESS_OTHER = 3
	ACCESS_TYPE = {
		ACCESS_WIFI: 'WIFI',
		ACCESS_2G: '2G',
		ACCESS_3G: '3G',
		ACCESS_OTHER: '其他',
	}

	device_model = models.CharField(verbose_name='设备型号', max_length=255)
	time_zone = models.IntegerField(verbose_name='设备时区', default=8)
	device_name = models.CharField(verbose_name='设备名称', max_length=255)
	_mc = models.CharField(verbose_name='MAC地址', max_length=255, null=True, blank=True)
	height = models.IntegerField(verbose_name='屏幕高')
	width = models.IntegerField(verbose_name='屏幕宽')
	carrier = models.IntegerField(verbose_name='运营商', choices=CARRIER_TYPE.items(), default=CARRIER_YIDONG)
	device_board = models.CharField(max_length=255)
	device_manufacturer = models.CharField(verbose_name='设备生产商', max_length=255)
	cpu = models.CharField(verbose_name='设备CPU', max_length=255)
	device_id = models.CharField(verbose_name='设备的device_id', max_length=255)
	device_brand = models.CharField(verbose_name='设备品牌', max_length=255)
	_access = models.IntegerField(verbose_name='设备网络类型', max_length=255, choices=ACCESS_TYPE.items())
	access_subtype = models.CharField(verbose_name='设备网络类型描述', max_length=255)
	country = models.CharField(verbose_name='设备国家', max_length=255, default='CN')
	_idmd5 = models.CharField(verbose_name='设备ID的md5加密', max_length=255, null=True, blank=True, default=None)
	_device_manutime = models.DateTimeField(verbose_name='设备制造时间', null=True, blank=True, default=None)
	os = models.CharField(verbose_name='系统', max_length=30)
	os_version = models.CharField(verbose_name='系统版本号', max_length=100)
	device_manuid = models.CharField(verbose_name='系统固件版本号', max_length=255)
	language = models.CharField(verbose_name='系统语言', default='zh', max_length=50)
	_app = models.ForeignKey(AppInfo, verbose_name='应用')

	def __str__(self):
		return '%s: %s' % (self.device_model, self.device_name)

	@property
	def access(self):
		"""
		:return: 网络类型
		:rtype: str
		"""
		net_type = {
			self.ACCESS_3G: '2G/3G',
			self.ACCESS_2G: '2G/3G',
			self.ACCESS_WIFI: 'Wi-Fi',
			self.ACCESS_OTHER: '2G/3G',
		}
		return net_type[self._access]

	@property
	def device_manutime(self):
		"""
		设备制造时间1970-01-01开始的毫秒数
		"""
		now = DatetimeUtils.now_local()
		first = datetime.datetime(year=1970, month=1, day=1, hour=0, minute=0, second=0, tzinfo=now.tzinfo)
		if not self._device_manutime:
			self._device_manutime = now - datetime.timedelta(days=random.randint(30, 180))
			self.save()
		return int((self._device_manutime - first).total_seconds() * 1000)

	@property
	def resolution(self):
		"""
		设备分辨率
		"""
		return '%s*%s' % (self.height, self.width)

	@property
	def idmd5(self):
		"""
		加密后的设备id
		"""
		if not self._idmd5:
			self._idmd5 = hashlib.md5(self.device_id).hexdigest()
			self.save(update_fields=['_idmd5'])
		return self._idmd5

	@property
	def mc(self):
		"""
		设备MAC地址
		"""
		if not self._mc:
			self._mc = ':'.join(['%02x' % random.randint(0, 255) for i in range(6)])
			self.save(update_fields=['_mc'])
		return self._mc

	@classmethod
	def genera_access_subtype(cls, access):
		"""
		:return: 网络子类型
		:rtype: str
		"""
		subtype = {
			cls.ACCESS_3G: ['CDMA', 'LTE', 'HSPA'],
			cls.ACCESS_2G: ['EDGE'],
			cls.ACCESS_WIFI: ['Unknown'],
			cls.ACCESS_OTHER: ['Unknown', 'HSPA+', 'TDMA']
		}
		return random.choice(subtype[access])

	@classmethod
	def genera_random_str(cls, str_length):
		"""
		生成随机长度字符串
		"""
		import string
		return ''.join(random.sample(string.ascii_letters, str_length))

	@classmethod
	def make_phone(cls, device, app=None):
		"""
		:return: 手机对象
		:rtype: PhoneDevice
		"""
		access = random.choice(cls.ACCESS_TYPE.keys())
		if not app:
			app = AppInfo.objects.all()[0]
		phone = cls(
			device_model=device.device_model,
			device_name=cls.genera_random_str(8),
			height=device.height,
			width=device.width,
			carrier=random.choice(cls.CARRIER_TYPE.keys()),
			device_board=cls.genera_random_str(8),
			device_manufacturer=device.device_manufacturer,
			cpu=device.cpu,
			device_id=hashlib.md5(cls.genera_random_str(20) + str(DatetimeUtils.now_local())).hexdigest(),
			device_brand=device.device_brand,
			_access=access,
			access_subtype=cls.genera_access_subtype(access),
			os=device.os,
			os_version=device.os_version,
			device_manuid=device.device_manuid,
			_app=app
		)
		phone.save()
		return phone


class VirtualUser(models.Model):
	"""
	虚拟用户
	"""

	CONNECT_QQ = 0
	CONNECT_SINA = 1
	CONNECT_CATEGORY_CHOICE = {
		CONNECT_QQ: 'QQ',
		CONNECT_SINA: '新浪',
	}

	device = models.ForeignKey(PhoneDevice, verbose_name='用户设备')
	launch_datetime = models.DateTimeField(verbose_name='本次启动时间戳', auto_now_add=True, null=True, blank=True, default=None, db_index=True)
	launch_session_id = models.CharField(verbose_name='本次启动的会话ID', max_length=255)
	terminate_duration = models.IntegerField(verbose_name='上次使用的时长(秒)', default=0)
	terminate_session_id = models.CharField(verbose_name='上次启动的会话ID', null=True, blank=True, max_length=255, default=None)
	terminate_datetime = models.DateTimeField(verbose_name='上次使用的时间戳', null=True, blank=True, default=None)
	today_online_seconds = models.IntegerField(verbose_name='当日已在线时长', default=0)
	online_days = models.IntegerField(verbose_name='持续在线天数', default=0)
	create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
	province = models.IntegerField(verbose_name='省份', choices=province_manage.items())
	channel = models.CharField(verbose_name='渠道', max_length=100)
	connect_id = models.CharField(verbose_name='连接登录编号', max_length=128, unique=True)
	connect_category = models.IntegerField(verbose_name='连接登录类型', choices=CONNECT_CATEGORY_CHOICE.items(), default=CONNECT_QQ)

	@staticmethod
	def make_session():
		"""
		:return: session_id
		"""
		return hashlib.md5(str(DatetimeUtils.now_local()) + 'dayi').hexdigest()

	def make_next_online(self):
		"""
		:return: 下次上线时间节点数据
		:rtype: KeyTimeNode
		"""
		if self.terminate_datetime:
			base_time = self.terminate_datetime
		else:
			base_time = DatetimeUtils.now_utc()
		nodes = KeyTimeNode.objects.filter(user=self, key_time__gt=base_time).order_by('key_time')
		if nodes:
			self.launch_datetime = nodes[0].key_time
			self.terminate_duration = nodes[0].duration
		else:
			self.launch_datetime = None
		self.save()

	@property
	def launch_date(self):
		"""
		本次启动日期
		"""
		return self.launch_datetime.strftime('%Y-%m-%d') if self.launch_datetime else ''

	@property
	def launch_time(self):
		"""
		本次启动的时间
		"""
		return self.launch_datetime.strftime('%H:%M:%S') if self.launch_datetime else ''

	@property
	def terminate_date(self):
		"""
		本次启动日期
		"""
		return self.terminate_datetime.strftime('%Y-%m-%d') if self.terminate_datetime else ''

	@property
	def terminate_time(self):
		"""
		本次启动的时间
		"""
		return self.terminate_datetime.strftime('%H:%M:%S') if self.terminate_datetime else ''

	def online(self, update_session=False):
		"""
		应用上线
		:param update_session: 是否更新session
		:param timestamp: 设备上线的时间戳
		"""
		if update_session:
			self.launch_session_id = self.make_session()
		if not self.terminate_session_id:
			self.terminate_session_id = self.launch_session_id
		self.save()

	def offline(self):
		"""
		应用下线
		:param timestamp: 设备下线时间戳
		:param duration: 设备在线时长（分)
		"""
		self.terminate_datetime = self.launch_datetime
		self.save()

	def launch(self, activities, update_session=False, protocol_class=BaiduProtocol):
		"""
		启动设备, 发送统计请求
		:param activities: 页面访问路径
		:param update_session: 是否更新sessoin_id
		"""
		from .proxy import urlopen_with_location

		self.online(update_session)

	#  发送统计请求
		url, payload, headers = protocol_class.assemble(self, activities)
		try:
			urlopen_with_location(url, payload, location=self.province, headers=headers, method='POST')
		except Exception as ex:
			err_log.exception('launch[%s]: %s' % (protocol_class, str(ex)))
		self.offline()

	# 连接登录
		from urllib import urlencode
		connect_login_url = 'http://121.43.67.92/api/app/student/ios/connect_login/'
		data = dict(
			connect_category=self.connect_category,
			connect_id=self.connect_id,
			extra=1,
			device_info=self.device.device_model,
			source=self.channel,
			appid='im.dayi.app.student',
		)
		urlopen_with_location(connect_login_url, urlencode(data), method='POST')


	@classmethod
	def create_new(cls, count):
		"""
		:param count: 指定生成数量
		:return: 生成的新用户列表
		:rtype: list
		"""
		from tools import random

		users_new = []
		for i in range(count):
			while True:
				connect_id = random.random_str(32, consts.DIGITAL_CHAR_MAP + consts.UPPER_ALPHA_MAP)
				if not cls.objects.filter(connect_id=connect_id):
					break

			u = cls(
				device=PhoneDevice.make_phone(Device.get_device_random()),
				launch_session_id=cls.make_session(),
				province=province_manage.get_province_random().index,
				channel=channel_data.dataset.random_data,
				connect_id=connect_id,
			)
			u.save()
			KeyTimeNode.make_key_times(u)
			u.make_next_online()
			users_new.append(u)
			run_log.info('create user %s / %s' % (i + 1, count))
		return users_new


class KeyTimeNode(models.Model):
	"""
	时间节点
	"""
	user = models.ForeignKey(VirtualUser, verbose_name='用户')
	key_time = models.DateTimeField(verbose_name='节点时间')
	duration = models.IntegerField(verbose_name='在线时间，单位秒')

	@classmethod
	def make_key_time_range(cls, date):
		"""
		:param date: 今日的date对象
		:return: 时间节点所在区间
		"""
		now = DatetimeUtils.now_utc()
		dt = datetime.datetime(year=date.year, month=date.month, day=date.day, hour=0, minute=0, second=0, tzinfo=now.tzinfo)
		return DatetimeUtils.get_day_range(dt, utc=True)

	@classmethod
	def has_key_times_today(cls, user):
		"""
		用户今天是否有关键节点
		:return: 时间点个数
		:rtype: int
		"""
		today = datetime.date.today()
		trange = cls.make_key_time_range(today)
		r = cls.objects.filter(user=user, key_time__range=trange)
		return r.count()

	@classmethod
	def clear_all(cls):
		"""
		清空所有数据
		"""
		cls.objects.all().delete()

	@classmethod
	def make_key_times(cls, user, valid_check=False):
		"""
		生成登陆时间节点
		:param user: 用户
		:param valid_check: 是否检查用户有无上线时间节点
		"""
		if valid_check and cls.has_key_times_today(user):
			return
		else:
			dest_count = random.randint(5, 10)
			has_count = cls.has_key_times_today(user)
			if dest_count - has_count > 0:
				times = access_manage.make_key_times(dest_count - has_count)
				for t in times:
					r = cls(user=user, key_time=t[0], duration=t[1])
					r.save()


class UserKeepStatistic(models.Model):
	"""
	用户留存统计
	"""
	create_date = models.DateField(verbose_name='首次使用日期', auto_now_add=True)
	percent = models.FloatField(verbose_name='留存率')
	days = models.IntegerField(verbose_name='统计节点')

	@classmethod
	def get_keep_percent(cls, date, days):
		"""
		:param date: 首次使用日期
		:param days: 统计节点
		:return: 留存纪录
		:rtype: UserKeepTask or None
		"""
		try:
			return cls.objects.get(create=date, days=days)
		except cls.DoesNotExist:
			return None

	@classmethod
	def statistic(cls, max_distance=5):
		"""
		统计近max_distance天注册用户在今日的留存率
		:param max_distance: 统计深度
		"""
		today = datetime.date.today()
		for i in range(1, max_distance + 1):
			day = today - datetime.timedelta(days=i)
			start_dt, end_dt = KeyTimeNode.make_key_time_range(day)
			users = VirtualUser.objects.filter(create_time__range=[start_dt, end_dt])
			total_count = users.count()
			keep_users = users.filter(online_days=i)
			keep_count = keep_users.count()
			cls(create_date=day, days=i, percent=round(keep_count / float(total_count), 3)).save()

	@classmethod
	def get_keep_users(cls, date):
		"""
		:param date: 首次使用日期
		:return: 首次使用日期到当日的留存用户
		:rtype: QuerySet
		"""
		keep_days = (datetime.date - date).days
		return cls.objects.filter(create_date=date, days=keep_days)

	@classmethod
	def simulate_keep_users(cls, date, keep_percent):
		"""
		模拟用户留存
		:param date: 首次使用日期
		:param keep_percent: 留存率
		:return: 明日将要留存的用户
		:rtype: set
		"""
		start_dt, end_dt = KeyTimeNode.make_key_time_range(date)
		users = VirtualUser.objects.filter(create_time__range=[start_dt, end_dt])
		total_count = users.count()

		# today_keep_users = list(cls.get_keep_users(date))
        #
		# today_keep_percent = len(today_keep_users) / float(total_count)
		# if keep_percent > today_keep_percent:
		# 	raise ValueError('目标留存率不能大于今日留存率')

		tomorrow_keep_users = random.sample(users, int(total_count * keep_percent))

		return tomorrow_keep_users


class StatisticTask(models.Model):
	"""
	统计任务
	"""
	TASK_USER_CREATE = 0
	TASK_USER_KEEP = 1
	TASK_CATEGORY = {
		TASK_USER_CREATE: '用户新增任务',
		TASK_USER_KEEP: '用户留存任务',
	}

	task_date = models.DateField(verbose_name='任务日期')
	task_category = models.IntegerField(verbose_name='任务类型')
	task_amount = models.FloatField(verbose_name='任务量')

	@classmethod
	def get_task(cls, category, date):
		"""
		获取任务
		:param category: 任务类型
		:param date: 任务日期
		:return: 任务量对象
		:rtype: StatisticTask or None
		"""
		try:
			return cls.objects.get(task_category=category, task_date=date)
		except cls.DoesNotExist:
			return None
